<?php
/**
 *
 *
 */
abstract class Yz_Item_Abstract
    extends Yz_Object
    implements Yz_Item_Interface
{

    /** Ref __dict__[id]
     *  @var reference
     */
    protected $id;

    /**
     *  @var array
     */
    protected $__dict__ = array('id' => null);

    /** Whether the item is isComplete ie. each property has its dict value set
     *  NOTE: this is checked as count(properties) == count(dict)
     *  @var bool
     */
    protected $isComplete = false;


    /** the model the item belongs to
     *  @var Yz_Model_Interface
     */
    protected $model;

    /** the item's properties
     *  @var array
     */
    protected $properties;


    /** the item of parent model
     *  @var Yz_Item_Interface
     */
    protected $parentItem;



    /************************************
     *          Public methods          *
     ************************************/

    /**
     *  @param Yz_Model_Interface $model
     *  @return ignored
     */
    public function __construct(Yz_Model_Interface $model, $id=null)
    {
        $this->id =& $this->__dict__['id'];
        $this->model = $model;
        $this->properties = $model->toArray();
        if ($id)
            $this->setId($id);
    }


    public function assignValues( $values, Yz_Item_Interface $parentItem=null )
    {
        debug();
        if ($this->isComplete)
            throw new Yz_Item_Exception('Item is complete');

        $my =& $this->__dict__;

        if (array_key_exists('id', $values)) {
            if (! $this->id)
                $this->setId($values['id']);
            unset($values['id']); // prevent doing it again later
        }

        if ($parentItem) {
            $this->parentItem = $parentItem;
            $my = $parentItem->__dict__; // also assign ID if any
            // NOTE: the properties defined in Parent have no values in $values
        }
        foreach ($this->properties as $propName => $prop) {
            // the ID property will be skipped
            if (! array_key_exists($propName, $values))
                continue;
            $my[$propName] = $prop->assignValue($values[$propName]);
        }
        $this->isComplete = count($my) == count($this->properties);
        return $this;
    }

    /** On cloning, force removal of the item's id
     *  @return ignored
     */
    public function __clone()
    {
        $this->id = null;
    }


    /** Return the item's string representation
     *  @return string
     */
    public function __toString()
    {
        $name = $this->_class_();
        $n = explode('\\', $name, 3);
        if (isset($n[2]))
            $name = strtr($n[2], '\\_', '..');
        $name .= '(' . $this->id . ')';
        return $name;
    }

    /**
     *
     */
    public function toArray()
    {
        $my =& $this->__dict__;
        if (! $this->isComplete)
            $this->init();
        return $my;
    }


    /** Item is empty if it has only one key in dict, which is always ID
     *  @return bool
     */
    public function isEmpty()
    {
        return count($this->__dict__) == 1;
    }

    /**
     *
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     *
     */
    public function setId($id)
    {
        if ($this->id)
            throw new Yz_Item_Exception("Can not change id from {$this->id} to {$id}");
        $this->id = $this->properties['id']->testValue($id);
        return $this;
    }

    /** describe an item, i.e. get its model
     *  @return Yz_Model_Interface $model
     */
    final public function describe()
    {
        return $this->model;
    }


    /**
     *  get item's title from its 'title' or 'name' value, or from its tostring method
     *  @return string
     *  @todo must modify the method to allow custom getters to be used
     */
    public function getTitle()
    {
        if (! $this->isComplete)
            $this->init();
        $v = $this->__dict__;
        if (array_key_exists('title', $v))
            $title = $v['title'];
        else if (array_key_exists('name', $v))
            $title = $v['name'];
        else
            $title = $this->__toString();
        return $title;
    }

    /**
     *  save item to the model
     *      Use model's update/add depending on whether the ID is empty
     *  @return this
     */
    public function fetchNeighbors()
    {
        if (! $this->id)
            throw new Yz_Exception('No neighbors without id');
        return $this->model->fetchNeighbors($this);
    }

    /**
     *  save item to the model
     *      Use model's update/add depending on whether the ID is empty
     *  @return this
     */
    public function save()
    {
        if ($this->id)
            $this->model->updateItem($this);
        else
            $this->model->addItem($this);
        return $this;
    }

    /**
     *  delete item from the model
     *  @return this
     */
    public function delete()
    {
        if (! $this->id)
            throw new Yz_Exception('Can not delete item without id');
        $this->model->deleteItem($this);
        return null;
    }


    /*******************************
     *      Protected methods      *
     *******************************/

    /** test the value being set, using the property's testValue()
     *  @return this
     *  @throw InaccessibleProperty
     */
    protected function _get_( $var )
    {
        if (! isset($this->properties[$var]))
            $this->undefinedVariable( $var, 'get' );
        if (! $this->isComplete)
            $this->init();
        $my =& $this->__dict__;
        if (array_key_exists($var, $my))
            $v = $my[$var];
        else
            $my[$var] = $v = $this->_default_($var);
        return $v;
    }

    /** test the value being set, using the property's testValue()
     *  @return this
     *  @throw InaccessibleProperty
     */
    protected function _set_( $var, $value )
    {
        // TODO all values which exist in parent item must get into it
        if (! isset($this->properties[$var]))
            $this->undefinedVariable( $var, 'set' );
        $prop = $this->properties[$var];
        try {
            $this->__dict__[$var] = $prop->testValue($value);
        } catch (Yz_Exception $ex) {
            throw Yz_Item_Exception_InvalidArgument::_new(array('property' => $var));
        }
        return $this;
    }

    /** forbid unsetting variables
     *  @throw InaccessibleProperty
     */
    protected function _del_( $var )
    {
        if (! isset($this->properties[$var]))
            $this->undefinedVariable( $var, 'unset' );
        $this->__dict__[$var] = $this->_default_($var);
        return $this;
    }


    /** Complete the item's values if they are not complete :)
     *  @return this
     */
    protected function init()
    {
        debug();
        if ($this->id) {
            // uninitialized item
            // load item from storage
            $this->model->fetchItemById($this->id);
        } else {
            $my =& $this->__dict__;
            foreach ($this->properties as $var => $prop)
                if (! array_key_exists($var, $my))
                    $my[$var] = $this->_default_($var);
        }
        return $this;
    }

    protected function _default_($var)
    {
        return $this->properties[$var]->selectValue($var, $this);
    }
}
